#include "settings.h"

#include <fstream>
#include <sstream>
#include <iterator>
#include <stdexcept>
#include <boost/lexical_cast.hpp>

#include "fastcgi2/config.h"

#include "details/config.h"
#include <xpath_static.h>

#ifdef HAVE_DMALLOC_H
#include <dmalloc.h>
#endif

#include <iostream>

using namespace TinyXPath;

namespace fastcgi
{

Config::Config()
{}

Config::~Config()
{
}

std::auto_ptr<Config>
Config::create(const char *file)
{
    return std::auto_ptr<Config>(new XmlConfig(file));
}

std::auto_ptr<Config>
Config::create(int &argc, char *argv[], HelpFunc func)
{
    for (int i = 1; i < argc; ++i) {
        if (strncmp(argv[i], "--config", sizeof("--config") - 1) == 0) {
            const char *pos = strchr(argv[i], '=');
            if (NULL != pos) {
                std::auto_ptr<Config> conf(new XmlConfig(pos + 1));
                std::swap(argv[i], argv[argc - 1]);
                --argc;
                return conf;
            }
        }
    }
    std::stringstream stream;
    if (NULL != func) {
        func(stream);
    } else {
        stream << "usage: fastcgi-daemon --config=<config file>";
    }
    throw std::logic_error(stream.str());
}

const std::string&
Config::filename() const
{
    return filename_;
}

void
Config::setFilename(const std::string &name)
{
    filename_ = name;
}

XmlConfig::XmlConfig(const char *file) :
    doc_(NULL), regex_("\\."/*"\\$\\{([A-Za-z][A-Za-z0-9\\-]*)\\}"*/)
{
    try {
        std::ifstream f(file);
        if (!f) {
            throw std::runtime_error(std::string("can not open ").append(file));
        }

        setFilename(file);

        doc_ = XmlDocHelper(XmlUtils::xmlParseFile(file));
        XmlUtils::throwUnless(NULL != doc_.get());
        root_ = XmlNodeHelper(XmlUtils::xmlDocGetRootElement(doc_.get()));
        if (NULL == root_.get()) {
            throw std::logic_error("got empty config");
        }
        findVariables(doc_);
    } catch (const std::ios::failure &e) {
        throw std::runtime_error(std::string("can not read ").append(file));
    }
}

XmlConfig::~XmlConfig()
{
}

int
XmlConfig::asInt(const std::string &key) const
{
    return boost::lexical_cast<int>(asString(key));
}

int
XmlConfig::asInt(const std::string &key, int defval) const
{
    try {
        return asInt(key);
    } catch (const std::exception &e) {
        return defval;
    }
}

std::string
XmlConfig::asString(const std::string &key) const
{

    std::string res;
    res.assign(S_xpath_string(root_.get(), key.c_str()).c_str());
    resolveVariables(res);
    return res;
}

std::string
XmlConfig::asString(const std::string &key, const std::string &defval) const
{
    try {
        return asString(key);
    } catch (const std::exception &e) {
        return defval;
    }
}

void
XmlConfig::subKeys(const std::string &key, std::vector<std::string> &v) const
{
    std::string count_key("count(" + key + ")");
    int count = i_xpath_int(root_.get(), count_key.c_str());
    v.reserve(count);
    for (int i = 0; i < count; ++i) {
        std::stringstream stream;
        stream << key << "[" << (i + 1) << "]";
        v.push_back(stream.str());
    }
}

void
XmlConfig::findVariables(const XmlDocHelper &doc)
{
    std::string count_key("count(/fastcgi/variables/variable)");
    int count = i_xpath_int(root_.get(),count_key.c_str());

    if (0 != count) {
        for (int i = 0; i < count; ++i) {
            std::stringstream stream;
            stream << "/fastcgi/variables/variable[" << (i + 1) << "]";
            xmlNodePtr node = XNp_xpath_node(root_.get(), stream.str().c_str());
            XmlUtils::throwUnless(NULL != node);
            const char *val = XmlUtils::value(node);
            const char *name = XmlUtils::attrValue(node, "name");

            if (NULL == val || NULL == name) {
                throw std::logic_error("bad variable definition");
            }
            vars_.insert(std::pair<std::string, std::string>(name, val));
        }
    }
}

void
XmlConfig::resolveVariables(std::string &val) const
{
    std::smatch res;
    while (std::regex_search(val, res, regex_)) {
        if (2 == res.size()) {
            std::string key(res[1].first, res[1].second);
            val.replace(res.position(static_cast<std::smatch::size_type>(0)), res.length(0), findVariable(key));
        }
    }
}

const std::string&
XmlConfig::findVariable(const std::string &key) const
{
    std::map<std::string, std::string>::const_iterator i = vars_.find(key);
    if (vars_.end() != i) {
        return i->second;
    } else {
        throw std::runtime_error(std::string("nonexistent variable ").append(key));
    }
}

} // namespace fastcgi
